/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    Stars.exe
    FILE:         FlightDeck.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Everything needed to launch and recover space craft...
*/

#include "MemDebug.h"
#include "FlightDeck.h"
#include "Ship.h"
#include "ShipCtrl.h"
#include "ShipDesign.h"
#include "Element.h"
#include "Mission.h"
#include "MissionEvent.h"
#include "Drive.h"
#include "Sim.h"
#include "Instruction.h"
#include "Hoop.h"
#include "LandingGear.h"
#include "RadioMessage.h"
#include "RadioTraffic.h"
#include "SimEvent.h"
#include "AudioConfig.h"
#include "CameraDirector.h"
#include "Combatant.h"
#include "CombatGroup.h"
#include "CombatUnit.h"

#include "NetData.h"
#include "NetUtil.h"

#include "Game.h"
#include "Solid.h"
#include "Light.h"
#include "Sound.h"
#include "DataLoader.h"

static Sound* tire_sound      = 0;
static Sound* catapult_sound  = 0;

// +======================================================================+

class FlightDeckSlot
{
public:
    FlightDeckSlot() : ship(0), state(0), sequence(0), time(0), filter(0xf) { }
    int operator == (const FlightDeckSlot& that) const { return this == &that; }

    Ship*             ship;
    Point             spot_rel;
    Point             spot_loc;

    int               state;
    int               sequence;
    double            time;
    double            clearance;
    DWORD             filter;
};

// +======================================================================+

InboundSlot::InboundSlot(Ship* s, FlightDeck* d, int squad, int index) 
: ship(s), deck(d), squadron(squad), slot(index), cleared(0), final(0), approach(0)
{
    if (ship)
    Observe(ship);

    if (deck && deck->GetCarrier())
    Observe((SimObject*) deck->GetCarrier());
}

int InboundSlot::operator < (const InboundSlot& that) const
{
    double dthis = 0;
    double dthat = 0;

    if (ship == that.ship) {
        return false;
    }

    if (cleared && !that.cleared) {
        return true;
    }

    if (!cleared && that.cleared) {
        return false;
    }

    if (ship && deck && that.ship) {
        dthis = Point(ship->Location()      - deck->MountLocation()).length();
        dthat = Point(that.ship->Location() - deck->MountLocation()).length();
    }

    if (dthis == dthat) {
        return (DWORD) this < (DWORD) &that;
    }

    return dthis < dthat;
}

int InboundSlot::operator <= (const InboundSlot& that) const
{
    double dthis = 0;
    double dthat = 0;

    if (ship == that.ship)
    return true;

    if (cleared && !that.cleared)
    return true;

    if (!cleared && that.cleared)
    return false;

    if (ship && deck && that.ship) {
        dthis = Point(ship->Location()      - deck->MountLocation()).length();
        dthat = Point(that.ship->Location() - deck->MountLocation()).length();
    }

    return dthis <= dthat;
}

int InboundSlot::operator == (const InboundSlot& that) const
{
    return this == &that;
}

void InboundSlot::Clear(bool c)
{
    if (ship)
    cleared = c;
}

double InboundSlot::Distance()
{
    if (ship && deck) {
        return Point(ship->Location() - deck->MountLocation()).length();
    }

    return 1e9;
}

// +----------------------------------------------------------------------+

bool
InboundSlot::Update(SimObject* obj)
{
    if (obj->Type() == SimObject::SIM_SHIP) {
        Ship* s = (Ship*) obj;

        if (s == ship) {
            ship = 0;
        }

        // Actually, this can't happen.  The flight deck
        // owns the slot.  When the carrier is destroyed,
        // the flight decks and slots are destroyed before
        // the carrier updates the observers.

        // I'm leaving this block in, just in case.

        else if (deck && s == deck->GetCarrier()) {
            ship = 0;
            deck = 0;
        }
    }

    return SimObserver::Update(obj);
}

const char*
InboundSlot::GetObserverName() const
{
    return "InboundSlot";
}

// +======================================================================+

FlightDeck::FlightDeck()
: System(FLIGHT_DECK, FLIGHT_DECK_LAUNCH, "Flight Deck", 1, 1),
carrier(0), index(0), num_slots(0), slots(0), cycle_time(5), num_hoops(0), hoops(0),
azimuth(0), light(0), num_catsounds(0), num_approach_pts(0)
{
    name = Game::GetText("sys.flight-deck");
    abrv = Game::GetText("sys.flight-deck.abrv");
}

// +----------------------------------------------------------------------+

FlightDeck::FlightDeck(const FlightDeck& s)
: System(s), 
carrier(0), index(0), start_rel(s.start_rel),
end_rel(s.end_rel), cam_rel(s.cam_rel),
cycle_time(s.cycle_time),
num_slots(s.num_slots), slots(0),
num_hoops(0), hoops(0), azimuth(s.azimuth), light(0),
num_catsounds(0), num_approach_pts(s.num_approach_pts)
{
    Mount(s);

    slots = new(__FILE__,__LINE__) FlightDeckSlot[num_slots];
    for (int i = 0; i < num_slots; i++) {
        slots[i].spot_rel = s.slots[i].spot_rel;
        slots[i].filter   = s.slots[i].filter;
    }

    for (int i = 0; i < NUM_APPROACH_PTS; i++)
    approach_rel[i] = s.approach_rel[i];

    for (int i = 0; i < 2; i++)
    runway_rel[i] = s.runway_rel[i];

    if (s.light) {
        light = new(__FILE__,__LINE__) Light(*s.light);
    }
}

// +--------------------------------------------------------------------+

FlightDeck::~FlightDeck()
{
    if (hoops && num_hoops) {
        for (int i = 0; i < num_hoops; i++) {
            Hoop*  h     = &hoops[i];
            Scene* scene = h->GetScene();
            if (scene)
            scene->DelGraphic(h);
        }
    }

    delete [] slots;
    delete [] hoops;

    LIGHT_DESTROY(light);

    recovery_queue.destroy();
}

// +--------------------------------------------------------------------+

void
FlightDeck::Initialize()
{
    static int initialized = 0;
    if (initialized) return;

    DataLoader* loader = DataLoader::GetLoader();
    loader->SetDataPath("Sounds/");

    const int SOUND_FLAGS = Sound::LOCALIZED |
    Sound::LOC_3D;

    loader->LoadSound("Tires.wav",      tire_sound,       SOUND_FLAGS);
    loader->LoadSound("Catapult.wav",   catapult_sound,   SOUND_FLAGS);
    loader->SetDataPath(0);

    if (tire_sound)
    tire_sound->SetMaxDistance(2.5e3f);

    if (catapult_sound)
    catapult_sound->SetMaxDistance(0.5e3f);

    initialized = 1;
}

// +--------------------------------------------------------------------+

void
FlightDeck::Close()
{
    delete tire_sound;
    delete catapult_sound;

    tire_sound     = 0;
    catapult_sound = 0;
}

// +--------------------------------------------------------------------+

void
FlightDeck::ExecFrame(double seconds)
{
    System::ExecFrame(seconds);

    bool advance_queue = false;
    long max_vol       = AudioConfig::EfxVolume();
    long volume        = -1000;
    Sim* sim           = Sim::GetSim();

    if (volume > max_vol)
    volume = max_vol;

    // update ship status:
    for (int i = 0; i < num_slots; i++) {
        FlightDeckSlot* slot = &slots[i];
        Ship*           slot_ship = 0;

        if (slot->ship == 0) {
            slot->state = CLEAR;
        }
        else {
            slot_ship = slot->ship;
            slot_ship->SetThrottle(0);
        }

        switch (slot->state) {
        case CLEAR:
            if (slot->time > 0) {
                slot->time -= seconds;
            }
            else if (IsRecoveryDeck()) {
                GrantClearance();
            }
            break;

        case READY: {
                Camera c;
                c.Clone(carrier->Cam());
                c.Yaw(azimuth);

                if (slot_ship) {
                    slot_ship->CloneCam(c);
                    slot_ship->MoveTo(slot->spot_loc);
                    slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);
                    slot_ship->SetVelocity(carrier->Velocity());
                }
                slot->time = 0;
            }
            break;

        case QUEUED:
            if (slot->time > 0) {
                Camera c;
                c.Clone(carrier->Cam());
                c.Yaw(azimuth);

                slot->time -= seconds;
                if (slot_ship) {
                    slot_ship->CloneCam(c);
                    slot_ship->MoveTo(slot->spot_loc);
                    slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);
                    slot_ship->SetFlightPhase(Ship::ALERT);
                }
            }

            if (slot->sequence == 1 && slot->time <= 0) {
                bool clear_for_launch = true;
                for (int i = 0; i < num_slots; i++)
                if (slots[i].state == LOCKED)
                clear_for_launch = false;

                if (clear_for_launch) {
                    slot->sequence = 0;
                    slot->state    = LOCKED;
                    slot->time     = cycle_time;
                    if (slot_ship)
                    slot_ship->SetFlightPhase(Ship::LOCKED);
                    num_catsounds     = 0;

                    advance_queue  = true;
                }
            }
            break;

        case LOCKED:
            if (slot->time > 0) {
                slot->time -= seconds;

                if (slot_ship) {
                    double ct4 = cycle_time/4;

                    double dx   = start_rel.x - slot->spot_rel.x;
                    double dy   = start_rel.z - slot->spot_rel.z;
                    double dyaw = atan2(dx,dy) - azimuth;

                    Camera c;
                    c.Clone(carrier->Cam());
                    c.Yaw(azimuth);

                    // rotate:
                    if (slot->time > 3*ct4) {
                        double step = 1 - (slot->time - 3*ct4) / ct4;
                        c.Yaw(dyaw * step);
                        slot_ship->CloneCam(c);
                        slot_ship->MoveTo(slot->spot_loc);
                        slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);

                        if (carrier->IsGroundUnit()) {
                            slot_ship->SetThrottle(25);
                        }

                        else if (num_catsounds < 1) {
                            if (catapult_sound) {
                                Sound* sound = catapult_sound->Duplicate();
                                sound->SetLocation(slot_ship->Location());
                                sound->SetVolume(volume);
                                sound->Play();
                            }
                            num_catsounds = 1;
                        }
                    }

                    // translate:
                    else if (slot->time > 2*ct4) {
                        double step = (slot->time - 2*ct4) / ct4;

                        Point  loc  = start_point +
                        (slot->spot_loc - start_point) * step;

                        c.Yaw(dyaw);
                        slot_ship->CloneCam(c);
                        slot_ship->MoveTo(loc);
                        slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);

                        if (carrier->IsGroundUnit()) {
                            slot_ship->SetThrottle(25);
                        }

                        else if (num_catsounds < 2) {
                            if (catapult_sound) {
                                Sound* sound = catapult_sound->Duplicate();
                                sound->SetLocation(slot_ship->Location());
                                sound->SetVolume(volume);
                                sound->Play();
                            }
                            num_catsounds = 2;
                        }
                    }

                    // rotate:
                    else if (slot->time > ct4) {
                        double step = (slot->time - ct4) / ct4;
                        c.Yaw(dyaw*step);
                        slot_ship->CloneCam(c);
                        slot_ship->MoveTo(start_point);
                        slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);

                        if (carrier->IsGroundUnit()) {
                            slot_ship->SetThrottle(25);
                        }

                        else if (num_catsounds < 3) {
                            if (catapult_sound) {
                                Sound* sound = catapult_sound->Duplicate();
                                sound->SetLocation(slot_ship->Location());
                                sound->SetVolume(volume);
                                sound->Play();
                            }
                            num_catsounds = 3;
                        }
                    }

                    // wait:
                    else {
                        slot_ship->SetThrottle(100);
                        slot_ship->CloneCam(c);
                        slot_ship->MoveTo(start_point);
                        slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance);
                    }

                    slot_ship->SetFlightPhase(Ship::LOCKED);
                }
            }
            else {
                slot->state    = LAUNCH;
                slot->time     = 0;

            }
            break;

        case LAUNCH:
            LaunchShip(slot_ship);
            break;

        case DOCKING:
            if (slot_ship && !slot_ship->IsAirborne()) {
                // do arresting gear stuff:
                if (slot_ship->GetFlightModel() == Ship::FM_ARCADE)
                slot_ship->ArcadeStop();

                slot_ship->SetVelocity(carrier->Velocity());
            }

            if (slot->time > 0) {
                slot->time -= seconds;
            }
            else {
                if (slot_ship) {
                    slot_ship->SetFlightPhase(Ship::DOCKED);
                    slot_ship->Stow();

                    NetUtil::SendObjKill(slot_ship, carrier, NetObjKill::KILL_DOCK, index);
                }

                Clear(i);
            }
            break;

        default:
            break;
        }
    }

    if (advance_queue) {
        for (int i = 0; i < num_slots; i++) {
            FlightDeckSlot* slot = &slots[i];
            if (slot->state == QUEUED && slot->sequence > 1)
            slot->sequence--;
        }
    }
}

bool
FlightDeck::LaunchShip(Ship* slot_ship)
{
    FlightDeckSlot* slot = 0;
    Sim*            sim  = Sim::GetSim();

    if (slot_ship) {
        for (int i = 0; i < num_slots; i++) {
            if (slots[i].ship == slot_ship)
            slot = &(slots[i]);
        }

        // only suggest a launch point if no flight plan has been filed...
        if (slot_ship->GetElement()->FlightPlanLength() == 0) {
            Point departure = end_point;
            departure = departure.OtherHand();

            Instruction* launch_point = new(__FILE__,__LINE__)
            Instruction(carrier->GetRegion(), departure, Instruction::LAUNCH);
            launch_point->SetSpeed(350);

            slot_ship->SetLaunchPoint(launch_point);
        }

        if (!slot_ship->IsAirborne()) {
            Point cat;
            if (fabs(azimuth) < 5*DEGREES) {
                cat = carrier->Heading() * 300;
            }
            else {
                Camera c;
                c.Clone(carrier->Cam());
                c.Yaw(azimuth);
                cat = c.vpn() * 300;
            }

            slot_ship->SetVelocity(carrier->Velocity() + cat);
            slot_ship->SetFlightPhase(Ship::LAUNCH);
        }
        else {
            slot_ship->SetFlightPhase(Ship::TAKEOFF);
        }

        Director* dir = slot_ship->GetDirector();
        if (dir && dir->Type() == ShipCtrl::DIR_TYPE) {
            ShipCtrl* ctrl = (ShipCtrl*) dir;
            ctrl->Launch();
        }

        ShipStats* c = ShipStats::Find(carrier->Name());
        if (c) c->AddEvent(SimEvent::LAUNCH_SHIP, slot_ship->Name());

        ShipStats* stats = ShipStats::Find(slot_ship->Name());
        if (stats) {
            stats->SetRegion(carrier->GetRegion()->Name());
            stats->SetType(slot_ship->Design()->name);

            if (slot_ship->GetElement()) {
                Element* elem = slot_ship->GetElement();
                stats->SetRole(Mission::RoleName(elem->Type()));
                stats->SetCombatGroup(elem->GetCombatGroup());
                stats->SetCombatUnit(elem->GetCombatUnit());
                stats->SetElementIndex(slot_ship->GetElementIndex());
            }

            stats->SetIFF(slot_ship->GetIFF());
            stats->AddEvent(SimEvent::LAUNCH, carrier->Name());

            if (slot_ship == sim->GetPlayerShip())
            stats->SetPlayer(true);
        }

        sim->ProcessEventTrigger(MissionEvent::TRIGGER_LAUNCH, 0, slot_ship->Name());

        if (slot) {
            slot->ship     = 0;
            slot->state    = CLEAR;
            slot->sequence = 0;
            slot->time     = 0;
        }

        return true;
    }

    return false;
}

// +----------------------------------------------------------------------+

void
FlightDeck::SetLight(double l)
{
    LIGHT_DESTROY(light);
    light = new(__FILE__,__LINE__) Light((float) l);
}

void
FlightDeck::SetApproachPoint(int i, Point loc)
{
    if (i >= 0 && i < NUM_APPROACH_PTS) {
        approach_rel[i] = loc;

        if (i >= num_approach_pts)
        num_approach_pts = i+1;
    }
}

void
FlightDeck::SetRunwayPoint(int i, Point loc)
{
    if (i >= 0 && i < 2)
    runway_rel[i] = loc;
}

void
FlightDeck::SetStartPoint(Point loc)
{
    start_rel = loc;
}

void
FlightDeck::SetEndPoint(Point loc)
{
    end_rel = loc;
}

void
FlightDeck::SetCamLoc(Point loc)
{
    cam_rel = loc;
}

// +----------------------------------------------------------------------+

void
FlightDeck::AddSlot(const Point& spot, DWORD filter)
{
    if (!slots)
    slots = new(__FILE__,__LINE__) FlightDeckSlot[10];

    slots[num_slots].spot_rel = spot;
    slots[num_slots].filter   = filter;
    num_slots++;
}

// +----------------------------------------------------------------------+

void
FlightDeck::SetCycleTime(double t)
{
    cycle_time = t;
}

// +----------------------------------------------------------------------+

void
FlightDeck::Orient(const Physical* rep)
{
    System::Orient(rep);

    Matrix        orientation = rep->Cam().Orientation();
    Point         loc         = rep->Location();

    start_point = (start_rel   * orientation) + loc;
    end_point   = (end_rel     * orientation) + loc;
    cam_loc     = (cam_rel     * orientation) + loc;

    for (int i = 0; i < num_approach_pts; i++)
    approach_point[i] = (approach_rel[i] * orientation) + loc;

    for (int i = 0; i < num_slots; i++)
    slots[i].spot_loc = (slots[i].spot_rel * orientation) + loc;

    if (IsRecoveryDeck()) {
        if (carrier->IsAirborne()) {
            runway_point[0] = (runway_rel[0] * orientation) + loc;
            runway_point[1] = (runway_rel[1] * orientation) + loc;
        }

        if (num_hoops < 1) {
            num_hoops = 4;
            hoops  = new(__FILE__,__LINE__) Hoop[num_hoops];
        }

        Point  hoop_vec = start_point - end_point;
        double hoop_d   = hoop_vec.Normalize() / num_hoops;

        orientation.Yaw(azimuth);

        for (int i = 0; i < num_hoops; i++) {
            hoops[i].MoveTo(end_point + hoop_vec * (i+1) * hoop_d);
            hoops[i].SetOrientation(orientation);
        }
    }

    if (light)
    light->MoveTo(mount_loc);
}

// +----------------------------------------------------------------------+

int
FlightDeck::SpaceLeft(int type) const
{
    int space_left = 0;

    for (int i = 0; i < num_slots; i++)
    if (slots[i].ship == 0 && (slots[i].filter & type))
    space_left++;

    return space_left;
}

// +----------------------------------------------------------------------+

bool
FlightDeck::Spot(Ship* s, int& index)
{
    if (!s)
    return false;

    if (index < 0) {
        for (int i = 0; i < num_slots; i++) {
            if (slots[i].ship == 0 && (slots[i].filter & s->Class())) {
                index = i;
                break;
            }
        }
    }

    if (index >= 0 && index < num_slots && slots[index].ship == 0) {
        slots[index].state     = READY;
        slots[index].ship      = s;
        slots[index].clearance = 0;

        if (s->GetGear())
        slots[index].clearance = s->GetGear()->GetClearance();

        if (IsRecoveryDeck() && !s->IsAirborne()) {
            s->SetVelocity(s->Velocity() * 0.01);   // hit the brakes!
        }

        if (!IsRecoveryDeck()) {
            Camera work;
            work.Clone(carrier->Cam());
            work.Yaw(azimuth);

            s->CloneCam(work);
            s->MoveTo(slots[index].spot_loc);

            LandingGear* g = s->GetGear();
            if (g) {
                g->SetState(LandingGear::GEAR_DOWN);
                g->ExecFrame(0);
                s->TranslateBy(carrier->Cam().vup() * slots[index].clearance);
            }

            s->SetFlightPhase(Ship::ALERT);
        }

        s->SetCarrier(carrier, this);
        Observe(s);

        return true;
    }

    return false;
}

bool
FlightDeck::Clear(int index)
{
    if (index >= 0 && index < num_slots && slots[index].ship != 0) {
        Ship* s = slots[index].ship;

        slots[index].ship  = 0;
        slots[index].time  = cycle_time;
        slots[index].state = CLEAR;

        ListIter<InboundSlot> iter = recovery_queue;
        while (++iter) {
            if (iter->GetShip() == s) {
                delete iter.removeItem(); // ??? SHOULD DELETE HERE ???
                break;
            }
        }

        return true;
    }

    return false;
}

// +----------------------------------------------------------------------+

bool
FlightDeck::Launch(int index)
{
    if (subtype == FLIGHT_DECK_LAUNCH && index >= 0 && index < num_slots) {
        FlightDeckSlot* slot = &slots[index];

        if (slot->ship && slot->state == READY) {
            int last = 0;
            FlightDeckSlot* last_slot = 0;
            FlightDeckSlot* lock_slot = 0;

            for (int i = 0; i < num_slots; i++) {
                FlightDeckSlot* s = &slots[i];

                if (s->state == QUEUED && s->sequence > last) {
                    last        = s->sequence;
                    last_slot   = s;
                }

                else if (s->state == LOCKED) {
                    lock_slot   = s;
                }
            }

            // queue the slot for launch:
            slot->state    = QUEUED;
            slot->sequence = last + 1;
            slot->time     = 0;

            if (last_slot)
            slot->time  = last_slot->time + cycle_time;

            else if (lock_slot)
            slot->time  = lock_slot->time;

            return true;
        }
    }

    return false;
}

// +----------------------------------------------------------------------+

bool
FlightDeck::Recover(Ship* s)
{
    if (s && subtype == FLIGHT_DECK_RECOVERY) {
        if (OverThreshold(s)) {
            if (s->GetFlightPhase() < Ship::RECOVERY) {
                s->SetFlightPhase(Ship::RECOVERY);
                s->SetCarrier(carrier, this); // let ship know which flight deck it's in (needed for dock cam)
            }

            // are we there yet?
            if (s->GetFlightPhase() >= Ship::ACTIVE && s->GetFlightPhase() < Ship::DOCKING) {
                if (slots[0].ship == 0) { // only need to ask this on approach
                    double dock_distance = (s->Location() - MountLocation()).length();

                    if (s->IsAirborne()) {
                        double altitude = s->Location().y - MountLocation().y;
                        if (dock_distance < Radius()*3 && altitude < s->Radius())
                        Dock(s);
                    }
                    else {
                        if (dock_distance < s->Radius())
                        Dock(s);
                    }
                }
            }

            return true;
        }
        else {
            if (s->GetFlightPhase() == Ship::RECOVERY)
            s->SetFlightPhase(Ship::ACTIVE);
        }
    }

    return false;
}

bool
FlightDeck::Dock(Ship* s)
{
    if (s && subtype == FLIGHT_DECK_RECOVERY && slots[0].time <= 0) {
        int index = 0;
        if (Spot(s, index)) {
            s->SetFlightPhase(Ship::DOCKING);
            s->SetCarrier(carrier, this);

            // hard landings?
            if (Ship::GetLandingModel() == 0) {
                double base_damage = s->Design()->integrity;

                // did player do something stupid?
                if (s->GetGear() && !s->IsGearDown()) {
                    Print("FlightDeck::Dock(%s) Belly landing!\n", s->Name());
                    s->InflictDamage(0.5 * base_damage);
                }

                double docking_deflection = fabs(carrier->Cam().vup().y - s->Cam().vup().y);

                if (docking_deflection > 0.35) {
                    Print("Landing upside down? y = %.3f\n", docking_deflection);
                    s->InflictDamage(0.8 * base_damage);
                }

                // did incoming ship exceed safe landing parameters?
                if (s->IsAirborne()) {
                    if (s->Velocity().y < -20) {
                        Print("FlightDeck::Dock(%s) Slammed it!\n", s->Name());
                        s->InflictDamage(0.1 * base_damage);
                    }
                }

                // did incoming ship exceed safe docking speed?
                else {
                    Point delta_v = s->Velocity() - carrier->Velocity();
                    double excess = (delta_v.length() - 100);

                    if (excess > 0)
                    s->InflictDamage(excess);
                }
            }

            if (s->IsAirborne()) {
                if (s == Sim::GetSim()->GetPlayerShip() && tire_sound) {
                    Sound* sound = tire_sound->Duplicate();
                    sound->Play();
                }
            }

            if (s->GetDrive())
            s->GetDrive()->PowerOff();

            slots[index].state = DOCKING;
            slots[index].time  = 5;

            if (s->IsAirborne())
            slots[index].time = 7.5;
            return true;
        }
    }

    return false;
}

int
FlightDeck::Inbound(InboundSlot*& s)
{
    if (s && s->GetShip()) {
        Ship* inbound = s->GetShip();

        if (recovery_queue.contains(s)) {
            InboundSlot* orig = s;
            s = recovery_queue.find(s);
            delete orig;
        }
        else {
            recovery_queue.append(s);
            Observe(s->GetShip());
        }

        inbound->SetInbound(s);

        // find the best initial approach point for this ship:
        double current_distance = 1e9;
        for (int i = 0; i < num_approach_pts; i++) {
            double distance = Point(inbound->Location() - approach_point[i]).length();
            if (distance < current_distance) {
                current_distance = distance;
                s->SetApproach(i);
            }
        }

        Point offset(rand()-16000, rand()-16000, rand()-16000);
        offset.Normalize();
        offset *= 200;

        s->SetOffset(offset);

        // *** DEBUG ***
        // PrintQueue();

        // if the new guy is first in line, and the deck is almost ready,
        // go ahead and clear him for approach now
        recovery_queue.sort();
        if (recovery_queue[0] == s && !s->Cleared() && slots[0].time <= 3)
        s->Clear();

        return recovery_queue.index(s) + 1;
    }

    return 0;
}

void
FlightDeck::GrantClearance()
{
    if (recovery_queue.size() > 0) {
        if (recovery_queue[0]->Cleared() && recovery_queue[0]->Distance() > 45e3) {
            recovery_queue[0]->Clear(false);
        }

        if (!recovery_queue[0]->Cleared()) {
            recovery_queue.sort();

            if (recovery_queue[0]->Distance() < 35e3) {
                recovery_queue[0]->Clear();

                Ship* dst = recovery_queue[0]->GetShip();

                RadioMessage* clearance = new(__FILE__,__LINE__) RadioMessage(dst, carrier, RadioMessage::CALL_CLEARANCE);
                clearance->SetInfo(Text("for final approach to ") + Name());
                RadioTraffic::Transmit(clearance);
            }
        }
    }
}

// +----------------------------------------------------------------------+

void
FlightDeck::PrintQueue()
{
    Print("\nRecovery Queue for %s\n", Name());
    if (recovery_queue.size() < 1) {
        Print("  (empty)\n\n");
        return;
    }

    for (int i = 0; i < recovery_queue.size(); i++) {
        InboundSlot* s = recovery_queue.at(i);

        if (!s) {
            Print("  %2d. null\n", i);
        }
        else if (!s->GetShip()) {
            Print("  %2d. ship is null\n", i);
        }
        else {
            double d = Point(s->GetShip()->Location() - MountLocation()).length();
            Print("  %2d. %c %-20s %8d km\n", i, s->Cleared()?'*':' ', s->GetShip()->Name(), (int) (d/1000));
        }
    }

    Print("\n");
}

// +----------------------------------------------------------------------+

Ship*
FlightDeck::GetShip(int index) const
{
    if (index >= 0 && index < num_slots)
    return slots[index].ship;

    return 0;
}

double
FlightDeck::TimeRemaining(int index) const
{
    if (index >= 0 && index < num_slots)
    return slots[index].time;

    return 0;
}


int
FlightDeck::State(int index) const
{
    if (index >= 0 && index < num_slots)
    return slots[index].state;

    return 0;
}

int
FlightDeck::Sequence(int index) const
{
    if (index >= 0 && index < num_slots)
    return slots[index].sequence;

    return 0;
}

// +----------------------------------------------------------------------+

bool
FlightDeck::Update(SimObject* obj)
{
    if (obj->Type() == SimObject::SIM_SHIP) {
        Ship* s = (Ship*) obj;

        ListIter<InboundSlot> iter = recovery_queue;
        while (++iter) {
            InboundSlot* recovery_slot = iter.value();

            if (recovery_slot->GetShip() == s || recovery_slot->GetShip() == 0) {
                delete iter.removeItem();
            }
        }

        for (int i = 0; i < num_slots; i++) {
            FlightDeckSlot* slot = &slots[i];

            if (slot->ship == s) {
                slot->ship = 0;
                slot->state = 0;
                slot->sequence = 0;
                slot->time = 0;
                break;
            }
        }
    }

    return SimObserver::Update(obj);
}

const char*
FlightDeck::GetObserverName() const
{
    return Name();
}

// +----------------------------------------------------------------------+

bool
FlightDeck::OverThreshold(Ship* s) const
{
    if (carrier->IsAirborne()) {
        if (s->AltitudeAGL() > s->Radius() * 4)
        return false;

        const Point& sloc = s->Location();

        // is ship between the markers?
        double distance = 1e9;

        Point  d0      = runway_point[0] - sloc;
        Point  d1      = runway_point[1] - sloc;
        double d       = d0 * d1;
        bool   between = (d0 * d1) < 0;

        if (between) {
            // distance from point to line:
            Point src   = runway_point[0];
            Point dir   = runway_point[1] - src;
            Point w     = (sloc - src).cross(dir);

            distance = w.length() / dir.length();
        }

        return distance < Radius();
    }

    else {
        return (s->Location() - MountLocation()).length() < (s->Radius() + Radius());
    }
}

// +----------------------------------------------------------------------+

bool
FlightDeck::ContainsPoint(const Point& p) const
{
    return (p - MountLocation()).length() < Radius();
}
